package com.litelimit.core.service.aop;

import com.litelimit.core.limit.LiteLimitProps;
import com.litelimit.core.limit.annotation.LiteLimit;
import com.litelimit.core.service.exception.LiteLimitException;
import com.litelimit.core.strategy.LimitStrategyManager;
import lombok.AllArgsConstructor;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.web.bind.annotation.RequestMapping;

/**
 * 切面类
 */
@Aspect
@SuppressWarnings("ConstantConditions")
@AllArgsConstructor
public class LiteLimitAspect {

    private final LimitStrategyManager limitStrategyManager;
    private final LiteLimitProps liteLimitProps;

    // 所有的@Controller/@RestController类
    @Pointcut("@within(org.springframework.stereotype.Controller) || " +
            "@within(org.springframework.web.bind.annotation.RestController)")
    public void controllerPointcut(){}

    // @Controller/@RestController类上的注解
    @Pointcut("(within(@org.springframework.stereotype.Controller *) || " +
            "within(@org.springframework.web.bind.annotation.RestController *)) && " +
            "@within(com.litelimit.core.limit.annotation.LiteLimit) && " +
            "execution(public * *(..))")
    public void classPointcut(){}

    // 方法上的注解，并且有@RequestMapping/@GetMapping等
    @Pointcut("((within(@org.springframework.stereotype.Controller *) || " +
            "within(@org.springframework.web.bind.annotation.RestController *))) && " +
            "(@annotation(com.litelimit.core.limit.annotation.LiteLimit) && " +
            "@annotation(org.springframework.web.bind.annotation.RequestMapping) || " +
            "@annotation(org.springframework.web.bind.annotation.GetMapping) || " +
            "@annotation(org.springframework.web.bind.annotation.PostMapping) || " +
            "@annotation(org.springframework.web.bind.annotation.PutMapping) || " +
            "@annotation(org.springframework.web.bind.annotation.DeleteMapping) || " +
            "@annotation(org.springframework.web.bind.annotation.PatchMapping)) && " +
            "execution(public * *(..))")
    public void methodPointcut(){}

    @Around("classPointcut() || methodPointcut()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        // 取到最精确的注解（方法优先）
        LiteLimit limit = findLiteLimit(pjp);
        if (limit == null) {
            return pjp.proceed();   // 没注解直接放行
        }

        if (!limitStrategyManager.tryAcquire(limit)) {
            handleReject(limit);
        }

        return pjp.proceed();
    }

    /* 查找注解（方法 > 类） */
    private LiteLimit findLiteLimit(ProceedingJoinPoint pjp) {
        MethodSignature ms = (MethodSignature) pjp.getSignature();
        // 1. 先查方法
        RequestMapping requestMapping = AnnotationUtils.findAnnotation(ms.getMethod(), RequestMapping.class);
        if (requestMapping == null){
            return null;
        }
        LiteLimit methodAnno = AnnotationUtils.findAnnotation(ms.getMethod(), LiteLimit.class);
        if (methodAnno != null) {
            return methodAnno;
        }
        // 2. 再查类
        return AnnotatedElementUtils.findMergedAnnotation(ms.getDeclaringType(), LiteLimit.class);
    }

    // 抛出异常，由调用方使用@ControllerAdvice同一处理
    private void handleReject(LiteLimit limit) {
        throw new LiteLimitException("请求繁忙，请稍后再试！");
    }
}
